import hmac
import json
from datetime import datetime

import requests
from django.db import transaction
from django.db.models import Sum, Count
from requests import RequestException

from gis.admin.decorators import list_func
from gis.common import track_logging
from gis.common.django_ext.models import paginate
from gis.common.exceptions import BizException
from gis.common.retry import retry
from itpay.core.agency import agency_service
from itpay.core.agency.const import (
    AgencyTransactionType,
    AgencyOrderStatusType,
    AgencyType,
    AgencyReportType,
)
from itpay.core.agency.errors import (
    ERROR_BALANCE_NOT_ENOUGH,
    ERROR_AGENCY_RECHARGE_APPLY_NOT_EXISTS,
    ERROR_AGREE_AGENCY_RECHARGE_STATUS,
    ERROR_AGENCY_BALANCE_NOT_ENOUGH,
    ERROR_REFUSE_AGENCY_RECHARGE_STATUS,
    ILLEGAL_PARAMETER,
)
from itpay.core.conf import conf_client, MaestroConf
from itpay.core.mch import service as mch_service
from itpay.core.recharge_order.const import OrderStatus, OrderCommentRank
from itpay.core.recharge_order.models import Order, Comment
from itpay.utils import export_file
from itpay.utils import maestro_tracker
from .models import BalanceTransDetail, RechargeOrder, Complaint

_LOGGER = track_logging.getLogger(__name__)


def check_trans_type(trans_type, amount):
    if trans_type in [
        AgencyTransactionType.AGENCY_ADMIN_IN_RECHARGE,
        AgencyTransactionType.AGENCY_IN_RECHARGE,
    ]:
        assert amount > 0
    if trans_type in [
        AgencyTransactionType.AGENCY_ADMIN_OUT_RECHARGE,
        AgencyTransactionType.AGENCY_OUT_USER_RECHARGE,
    ]:
        assert amount < 0


def add_bill(agency_id, trans_type, out_trans_no, amount, trans_info=None, remark=None):
    """
    :param agency_id:
    :param trans_type:
    :param out_trans_no:
    :param amount:
    :param trans_info:
    :param remark:
    :return:
    """
    assert isinstance(trans_type, AgencyTransactionType)
    check_trans_type(trans_type, amount)
    with transaction.atomic():
        agency = agency_service.get_agency_model_with_lock(agency_id)

        agency.balance += amount
        if agency.balance < 0:
            raise BizException(ERROR_BALANCE_NOT_ENOUGH)
        agency.save()

        BalanceTransDetail.objects.create(
            agency=agency,
            amount=amount,
            balance=agency.balance,
            trans_type=trans_type.value,
            out_trans_no=out_trans_no,
            trans_info=trans_info,
            remark=remark,
        )


def apply_recharge(agency_id, amount):
    """
    申请额度
    :param agency_id:
    :param amount:
    :return:
    """
    agency = agency_service.get_agency_by_id(agency_id)
    order = RechargeOrder(
        agency_id=agency_id,
        amount=amount,
        agency_type=agency["agency_type"],
        status=AgencyOrderStatusType.CREATE.value,
        before_amount=agency["balance"],
    )
    order.save()
    return order.to_dict()


@list_func
def list_apply_recharge(
    page_no,
    page_size,
    id=None,
    agency_type=None,
    parent_id=None,
    parent_name=None,
    status=None,
    agency_id=None,
    agency_name=None,
    created_at_begin=None,
    created_at_end=None,
    updated_at_begin=None,
    updated_at_end=None,
    order_by="-updated_at",
):
    query = RechargeOrder.objects.filter()
    if id:
        query = query.filter(pk=id)
    if agency_type:
        if not isinstance(agency_type, AgencyType):
            agency_type = AgencyType(int(agency_type))
        query = query.filter(agency_type=agency_type.value)
    if parent_id:
        query = query.filter(agency__parent_id=parent_id)
    if agency_id:
        query = query.filter(agency_id=agency_id)
    if agency_name:
        query = query.filter(agency__user_name__contains=agency_name)
    if parent_name:
        query = query.filter(agency__parent__user_name__contains=parent_name)
    if status:
        if not isinstance(status, AgencyOrderStatusType):
            status = AgencyOrderStatusType(int(status))
        query = query.filter(status=status.value)
    if created_at_begin:
        query = query.filter(created_at__gte=created_at_begin)
    if created_at_end:
        query = query.filter(created_at__lte=created_at_end)
    if updated_at_begin:
        query = query.filter(updated_at__gte=updated_at_begin)
    if updated_at_end:
        query = query.filter(updated_at__lte=updated_at_end)
    if order_by:
        query = query.order_by(order_by)
    return query.count(), [e.to_dict() for e in paginate(query, page_no, page_size)]


def _get_recharge_apply_by_id(apply_id, with_lock=False):
    if with_lock:
        apply = RechargeOrder.objects.select_for_update().filter(pk=apply_id).first()
    else:
        apply = RechargeOrder.objects.filter(pk=apply_id).first()
    if not apply:
        raise BizException(ERROR_AGENCY_RECHARGE_APPLY_NOT_EXISTS)
    return apply


def get_recharge_apply_by_id(apply_id):
    return _get_recharge_apply_by_id(apply_id).to_dict()


def agree_recharge(apply_id, reason=None):
    """
    同意代理申请额度
    :param apply_id:
    :return:
    """
    parent_id = None
    with transaction.atomic():
        apply = _get_recharge_apply_by_id(apply_id, with_lock=True)
        if apply.status != AgencyOrderStatusType.CREATE.value:
            raise BizException(ERROR_AGREE_AGENCY_RECHARGE_STATUS, apply.status)

        apply.status = AgencyOrderStatusType.SUCCESS.value
        apply.reason = reason
        apply.success_at = datetime.now()
        apply.save()

        trans_type = (
            AgencyTransactionType.AGENCY_ADMIN_IN_RECHARGE
            if apply.agency_type == AgencyType.ADMIN_AGENCY.value
            else AgencyTransactionType.AGENCY_IN_RECHARGE
        )
        add_bill(apply.agency_id, trans_type, apply_id, apply.amount)

        if apply.agency_type == AgencyType.NORMAL_AGENCY.value:
            parent_id = apply.agency.parent_id
            parent = agency_service.get_agency_by_id(parent_id)
            if parent["balance"] < apply.amount:
                raise BizException(ERROR_AGENCY_BALANCE_NOT_ENOUGH, parent["balance"])
            add_bill(
                apply.agency.parent_id,
                AgencyTransactionType.AGENCY_ADMIN_OUT_RECHARGE,
                apply_id,
                -apply.amount,
            )

    if apply.agency_type == AgencyType.NORMAL_AGENCY.value:
        try:
            maestro_tracker.track_maestro_agency_recharge_success(
                apply_id,
                int(datetime.strftime(apply.success_at, "%s")),
                apply.agency_id,
                apply.amount,
            )
        except Exception:
            _LOGGER.info(
                "maestro_tracker track_maestro_agency_recharge_success error {} ".format(
                    apply_id
                ),
                exc_info=True,
            )


def refuse_agency_recharge(apply_id, reason=None):
    """
    拒绝代理申请额度
    :param apply_id:
    :return:
    """
    with transaction.atomic():
        apply = _get_recharge_apply_by_id(apply_id, with_lock=True)
        if apply.status != AgencyOrderStatusType.CREATE.value:
            raise BizException(ERROR_REFUSE_AGENCY_RECHARGE_STATUS, apply.status)

        apply.status = AgencyOrderStatusType.REFUSE.value
        apply.reason = reason
        apply.save()


def chase_recharge(apply_id, reason=None):
    """
    追回代理申请额度
    :param apply_id:
    :return:
    """
    with transaction.atomic():
        apply = _get_recharge_apply_by_id(apply_id, with_lock=True)
        agency = agency_service.get_agency_model_with_lock(apply.agency_id)

        chase_amount = min(apply.amount, agency.balance)

        if apply.status != AgencyOrderStatusType.SUCCESS.value:
            raise BizException(ERROR_AGREE_AGENCY_RECHARGE_STATUS, apply.status)

        apply.status = AgencyOrderStatusType.CHASE.value
        apply.chase_amount = chase_amount
        apply.reason = reason
        apply.save()

        trans_type = (
            AgencyTransactionType.AGENCY_OUT_RECHARGE_CHASE
            if apply.agency_type == AgencyType.ADMIN_AGENCY.value
            else AgencyTransactionType.AGENCY_ADMIN_OUT_RECHARGE_CHASE
        )
        add_bill(apply.agency_id, trans_type, apply_id, -apply.chase_amount)

        if apply.agency_type == AgencyType.NORMAL_AGENCY.value:
            add_bill(
                apply.agency.parent_id,
                AgencyTransactionType.AGENCY_ADMIN_IN_RECHARGE_CHASE,
                apply_id,
                apply.chase_amount,
            )


def list_recharge_report(
    page_no,
    page_size,
    parent_id=None,
    parent_name=None,
    agency_id=None,
    agency_name=None,
    updated_at_start=None,
    updated_at_end=None,
    mch_id=None,
):
    """
    充值报表
    :param agency_id:
    :param updated_at_start:
    :param updated_at_end:
    :param mch_id:
    :return:
    """
    count, agency_chn_list = get_agency_mch_report(
        page_no,
        page_size,
        parent_id,
        parent_name,
        agency_id,
        agency_name,
        mch_id,
        updated_at_start,
        updated_at_end,
    )
    resp_items = list()
    for item in agency_chn_list:
        mch = mch_service.get_mch(item["mch_id"])
        item["mch_name"] = mch["name"]
        good_rank, normal_rank, bad_rank = _get_rank_count(
            item["mch_id"], item["agency_id"], item["updated_day"]
        )
        item["today_apply_amount"] = get_apply_amount_by_day(
            item["agency_id"], item["updated_day"]
        )
        item["comment"] = {"good": good_rank, "normal": normal_rank, "bad": bad_rank}
        complaint_num = get_complaint_num(
            item["mch_id"], item["agency_id"], item["updated_day"]
        )
        item["complaint"] = complaint_num
        resp_items.append(item)
    return count, resp_items


def get_complaint_num(mch_id, agency_id, updated_at):
    complaint = Complaint.objects.filter(mch_id=mch_id, agency_id=agency_id).all()
    complaint = complaint.filter(created_at__date=updated_at)
    return complaint.count()


def get_agency_mch_report(
    page_no,
    page_size,
    parent_id=None,
    parent_name=None,
    agency_id=None,
    agency_name=None,
    mch_id=None,
    updated_at_start=None,
    update_at_end=None,
):
    query = (
        Order.objects.extra(
            select={
                "updated_day": "DATE_FORMAT(recharge_order_order.updated_at,'%%Y-%%m-%%d')"
            }
        )
        .values("mch_id", "updated_day")
        .annotate(order_num=Count("*"), order_amount=Sum("amount"))
        .filter(status=OrderStatus.SUCCESS.value)
    )
    if agency_id:
        query = query.filter(agency_id=agency_id)
    if agency_name:
        query = query.filter(agency_name=agency_name)
    if parent_id:
        query = query.filter(agency__parent_id=parent_id)
    if parent_name:
        query = query.filter(agency__parent__user_name=parent_name)
    if mch_id:
        query = query.filter(mch_id=mch_id)
    if updated_at_start:
        query = query.filter(updated_at__gte=updated_at_start)
    if update_at_end:
        query = query.filter(updated_at__lt=update_at_end)
    return query.count(), paginate(query.order_by("-updated_day"), page_no, page_size)


def _get_rank_count(mch_id, agency_id, updated_day):
    good_rank = get_order_count(
        agency_id=agency_id,
        mch_id=mch_id,
        updated_day=updated_day,
        comment_rank=OrderCommentRank.GOOD.value,
    )
    normal_rank = get_order_count(
        agency_id=agency_id,
        mch_id=mch_id,
        updated_day=updated_day,
        comment_rank=OrderCommentRank.NORMAL.value,
    )
    bad_rank = get_order_count(
        agency_id=agency_id,
        mch_id=mch_id,
        updated_day=updated_day,
        comment_rank=OrderCommentRank.BAD.value,
    )
    return good_rank, normal_rank, bad_rank


def get_day_report(
    agency_id=None,
    agency_name=None,
    parent_id=None,
    parent_name=None,
    mch_id=None,
    time_type=None,
):
    order_amount = get_order_amount(
        agency_id, agency_name, parent_id, parent_name, mch_id, time_type=time_type
    )
    apply_amount = get_apply_amount(
        agency_id, agency_name, parent_id, parent_name, mch_id, time_type=time_type
    )
    return order_amount["order_amount_sum"], apply_amount["apply_amount_sum"]


def get_apply_amount(
    agency_id=None,
    agency_name=None,
    parent_id=None,
    parent_name=None,
    mch_id=None,
    time_type=None,
):
    query = RechargeOrder.objects.filter(status=AgencyOrderStatusType.SUCCESS.value)
    if agency_id:
        query = query.filter(agency_id=agency_id)
    if parent_id:
        query = query.filter(agency__parent_id=parent_id)
    if agency_name:
        query = query.filter(agency_name=agency_name)
    if parent_name:
        query = query.filter(agency__parent__user_name=parent_name)
    if mch_id:
        query = query.filter(mch_id=mch_id)
    if time_type:
        created_at = _get_start_time(time_type=time_type)
        if created_at:
            query = query.filter(updated_at__gte=created_at)
    amount_sum = query.aggregate(apply_amount_sum=Sum("amount"))
    if amount_sum["apply_amount_sum"] is None:
        amount_sum["apply_amount_sum"] = 0
    return amount_sum


def get_order_amount(
    agency_id=None,
    agency_name=None,
    parent_id=None,
    parent_name=None,
    mch_id=None,
    time_type=None,
):
    query = Order.objects.filter(status=OrderStatus.SUCCESS.value)
    if agency_id:
        query = query.filter(agency_id=agency_id)
    if parent_id:
        query = query.filter(agency__parent_id=parent_id)
    if agency_name:
        query = query.filter(agency_name=agency_name)
    if parent_name:
        query = query.filter(agency__parent__user_name=parent_name)
    if mch_id:
        query = query.filter(mch_id=mch_id)
    if time_type:
        created_at = _get_start_time(time_type=time_type)
        if created_at:
            query = query.filter(updated_at__gte=created_at)
    amount_sum = query.aggregate(order_amount_sum=Sum("amount"))
    if amount_sum["order_amount_sum"] is None:
        amount_sum["order_amount_sum"] = 0
    return amount_sum


def _get_start_time(time_type=None):
    assert not time_type or isinstance(time_type, AgencyReportType)
    start_time = None
    if time_type == AgencyReportType.DAY:
        today = datetime.today()
        start_time = datetime(today.year, today.month, today.day, 0, 0, 0)
    elif time_type == AgencyReportType.MONTH:
        today = datetime.today()
        start_time = datetime(today.year, today.month, 1, 0, 0, 0)
    return start_time


def list_agency_recharge_report(
    page_no=None,
    page_size=None,
    agency_id=None,
    mch_id=None,
    agency_name=None,
    parent_name=None,
    start_at=None,
    end_at=None,
):
    """
    后台代理报表
    :param page_no:
    :param page_size:
    :param agency_id:
    :param mch_id:
    :param agency_name:
    :param parent_name:
    :param start_at:
    :param end_at:
    :return:
    """
    count, report_list = get_agency_report(
        page_no,
        page_size,
        agency_id,
        mch_id,
        agency_name,
        parent_name,
        start_at,
        end_at,
    )
    resp_items = []
    for item in report_list:
        agency = agency_service.get_agency_by_id(item["agency_id"])
        item["agency_name"] = agency["user_name"]
        mch = mch_service.get_mch(item["mch_id"])
        item["mch_name"] = mch["name"]
        good_order_num = get_order_count(
            agency_id=item["agency_id"],
            mch_id=item["mch_id"],
            update_at_start=start_at,
            update_at_end=end_at,
            comment_rank=OrderCommentRank.GOOD.value,
        )
        item["feedback_rate"] = _get_feedback_rate(item["order_num"], good_order_num)
        complaint_num = _get_complaint_num(
            item["mch_id"], item["agency_id"], start_at=start_at, end_at=end_at
        )
        item["complaint"] = complaint_num
        resp_items.append(item)
    return count, resp_items


def get_order_count(
    agency_id=None,
    agency_name=None,
    mch_id=None,
    update_at_start=None,
    update_at_end=None,
    updated_day=None,
    comment_rank=None,
):
    comment = Comment.objects.all()
    if agency_id:
        comment = comment.filter(agency_id=agency_id)
    if agency_name:
        comment = comment.filter(agency__user_name=agency_name)
    if mch_id:
        comment = comment.filter(agency_id=agency_id)
    if update_at_start:
        comment = comment.filter(updated_at__gte=updated_day)
    if update_at_end:
        comment = comment.filter(updated_at__lt=update_at_end)
    if updated_day:
        comment = comment.filter(updated_at__date=updated_day)
    if comment_rank:
        comment = comment.filter(comment_rank=comment_rank)
    return comment.count()


def get_agency_report(
    page_no,
    page_size,
    agency_id=None,
    mch_id=None,
    agency_name=None,
    parent_name=None,
    updated_at_start=None,
    update_at_end=None,
    status=OrderStatus.SUCCESS,
):
    query = Order.objects.values("agency_id", "mch_id").annotate(
        order_num=Count("*"), order_amount=Sum("amount")
    )
    if agency_id:
        query = query.filter(agency_id=agency_id)
    if mch_id:
        query = query.filter(mch_id=mch_id)
    if agency_name:
        query = query.filter(agency__user_name=agency_name)
    if parent_name:
        query = query.filter(agency__parent__user_name=parent_name)
    if status:
        query = query.filter(status=status.value)
    if updated_at_start:
        query = query.filter(updated_at__gte=updated_at_start)
    if update_at_end:
        query = query.filter(updated_at__lt=update_at_end)
    return query.count(), paginate(query.order_by("-agency_id"), page_no, page_size)


def _get_feedback_rate(total, count):
    return "%.1f%%" % float(count / total * 100)


def _get_complaint_num(mch_id, agency_id, start_at=None, end_at=None):
    complaint = Complaint.objects.filter(mch_id=mch_id, agency_id=agency_id).all()
    if start_at:
        complaint = complaint.filter(created_at__gte=start_at)
    if end_at:
        complaint = complaint.filter(created_at__gte=end_at)
    return complaint.count()


def agency_report_export(
    page_no,
    page_size,
    mch_id=None,
    agency_name=None,
    parent_name=None,
    start_at=None,
    end_at=None,
):
    if page_size > conf_client.get_int("report_max_num", 5000):
        page_size = conf_client.get_int("report_max_num", 5000)
    _, export_data = list_agency_recharge_report(
        page_no,
        page_size,
        mch_id=mch_id,
        agency_name=agency_name,
        parent_name=parent_name,
        start_at=start_at,
        end_at=end_at,
    )
    cn_header = ["代理ID", "代理名称", "平台", "充值订单数", "协助玩家上分金额", "好评率", "投诉"]
    resp_items = []
    for item in export_data:
        resp_items.append(
            [
                item["agency_id"],
                item["agency_name"],
                item["mch_name"],
                item["order_num"],
                item["order_amount"],
                item["feedback_rate"],
                item["feedback_rate"],
            ]
        )
    return export_file.get_file_date(resp_items, cn_header)


def get_daily_report(
    agency_id=None, agency_name=None, parent_id=None, parent_name=None, mch_id=None
):
    day_order_amount, day_apply_amount = get_day_report(
        agency_id,
        agency_name,
        parent_id,
        parent_name,
        mch_id,
        time_type=AgencyReportType.DAY,
    )
    month_order_amount, month_apply_amount = get_day_report(
        agency_id,
        agency_name,
        parent_id,
        parent_name,
        mch_id,
        time_type=AgencyReportType.MONTH,
    )
    history_order_amount, history_apply_amount = get_day_report(
        agency_id, agency_name, parent_id, parent_name, mch_id
    )
    daily_report = dict()
    daily_report["day"] = {
        "order_amount": day_order_amount,
        "apply_amount": day_apply_amount,
    }
    daily_report["month"] = {
        "order_amount": month_order_amount,
        "apply_amount": month_apply_amount,
    }
    daily_report["history"] = {
        "order_amount": history_order_amount,
        "apply_amount": history_apply_amount,
    }
    return daily_report


def get_apply_amount_by_day(agency_id, date):
    query = (
        RechargeOrder.objects.filter(status=AgencyOrderStatusType.SUCCESS.value)
        .filter(agency_id=agency_id)
        .filter(updated_at__date=date)
    )
    amount_sum = query.aggregate(apply_amount_sum=Sum("amount"))
    if amount_sum["apply_amount_sum"] is None:
        amount_sum["apply_amount_sum"] = 0
    return amount_sum["apply_amount_sum"]


@retry(RequestException, tries=2)
def get_agency_stats_report(
    page_no,
    page_size,
    time_range,
    mch_ids,
    parent_ids,
    agency_ids,
    sort_by,
    user_type,
    mch_type,
):
    def generate_sign(params, secret):
        s = ""
        for k in sorted(params.keys()):
            s += "{}={}&".format(k, params[k])
        s = s[:-1]
        m = hmac.new(secret.encode("utf8"), s.encode("utf8"))
        sign = m.hexdigest().upper()
        return sign

    params = {
        "page_no": str(page_no),
        "page_size": str(page_size),
        "time_range": time_range,
        "mch_ids": mch_ids,
        "parent_ids": parent_ids,
        "agency_ids": agency_ids,
        "sort_by": sort_by,
        "user_type": user_type,
        "key": "mf_pay",
        "mch_type": mch_type,
    }
    params["sign"] = generate_sign(params, MaestroConf.key)
    url = "{}/api/mf_pay/agency/get_agency_stats/".format(MaestroConf.host)

    _LOGGER.info("get_agency_stats_report begin {} ,params {}".format(url, params))
    response = requests.get(url, params, timeout=3)
    _LOGGER.info("get_agency_stats_report end  response {}".format(response.text))

    res = json.loads(response.text)
    if res["msg"]:
        raise BizException(ILLEGAL_PARAMETER, res["msg"])

    return res["data"]["total_count"], res["data"]["list"]
